// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: css_style_declaration.cpp
// Description: CSSStyleDeclaration Class
//      Author: Ziming Li
//     Created: 2022-04-29
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#include "./css_style_declaration.h"

#include "blinkit/blink/renderer/bindings/core/qjs/qjs.h"
#include "blinkit/blink/renderer/core/css/parser/CSSPropertyParser.h"
#include "blinkit/blink/renderer/wtf/text/StringBuilder.h"

namespace blink {

// Check for a CSS prefix.
// Passed prefix is all lowercase.
// First character of the prefix within the property name may be upper or lowercase.
// Other characters in the prefix within the property name must be lowercase.
// The prefix within the property name must be followed by a capital letter.
static bool HasCSSPropertyNamePrefix(const std::string &propertyName, const char *prefix)
{
#if ENABLE(ASSERT)
    ASSERT('\0' != *prefix);
    for (const char *p = prefix; '\0' != *p; ++p)
        ASSERT(isASCIILower(*p));
    ASSERT(propertyName.length() > 0);
#endif

    if (toASCIILower(propertyName[0]) != prefix[0])
        return false;

    unsigned length = propertyName.length();
    for (unsigned i = 1; i < length; ++i)
    {
        if ('\0' == prefix[i])
            return isASCIIUpper(propertyName[i]);
        if (propertyName[i] != prefix[i])
            return false;
    }
    return false;
}

static String PropertyNameToCSSName(const std::string &name)
{
    const unsigned length = name.length();

    StringBuilder builder;
    builder.reserveCapacity(length);

    unsigned i = 0;
    bool hasSeenDash = false;

    if (HasCSSPropertyNamePrefix(name, "css"))
        i += 3;
    else if (HasCSSPropertyNamePrefix(name, "webkit"))
        builder.append('-');
    else if (isASCIIUpper(name[0]))
        return String();

    bool hasSeenUpper = isASCIIUpper(name[i]);

    builder.append(toASCIILower(name[i++]));

    for (; i < length; ++i)
    {
        UChar c = name[i];
        if (!isASCIIUpper(c))
        {
            if (c == '-')
                hasSeenDash = true;
            builder.append(c);
        }
        else
        {
            hasSeenUpper = true;
            builder.append('-');
            builder.append(toASCIILower(c));
        }
    }

    // Reject names containing both dashes and upper-case characters, such as "border-rightColor".
    if (hasSeenDash && hasSeenUpper)
        return String();

    return builder.toString();
}

static CSSPropertyID ParsePropertyID(JSContext *ctx, JSAtom atom)
{
    static std::unordered_map<std::string, CSSPropertyID> s_nameMap;

    std::string name = qjs::ToStdString(ctx, atom);

    auto it = s_nameMap.find(name);
    if (s_nameMap.end() != it)
        return it->second;

    String cssName = PropertyNameToCSSName(name);
    if (cssName.isEmpty())
    {
        ZLOG("PropertyNameToCSSName failed for '{}'!", name);
        return CSSPropertyInvalid;
    }

    CSSPropertyID propId = unresolvedCSSPropertyID(cssName);
    if (CSSPropertyInvalid != propId)
        s_nameMap.emplace(name, propId);
    else
        ZLOG("WARNING: Unresolved CSS property for '{}'('{}')!", cssName.stdUtf8(), name);
    return propId;
}

int CSSStyleDeclaration::GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSAtom prop)
{
    CSSPropertyID propId = ParsePropertyID(ctx, prop);
    if (CSSPropertyInvalid == propId)
        return 0;

    if (nullptr != desc)
    {
        String propValue = getPropertyValueInternal(propId);
        desc->flags = JS_PROP_NORMAL;
        desc->value = qjs::WrapString(ctx, propValue.isNull() ? emptyString() : propValue);
    }
    return 1;
}

int CSSStyleDeclaration::SetProperty(JSContext *ctx, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags)
{
    CSSPropertyID propId = ParsePropertyID(ctx, atom);
    if (CSSPropertyInvalid == propId)
        return -1;

    qjs::Context c(*ctx);
    setPropertyInternal(propId, emptyString(), qjs::ToWTFString(ctx, value), false, c);
    if (c.hadException())
    {
        c.ThrowIfNeeded();
        return -1;
    }

    return 1;
}

} // namespace blink
